﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Threading;
using Lime49.UI;
using LockCrypt.DAL;
using LockCrypt.Core;
using System.Windows.Interop;
using LockCrypt.Properties;
using Microsoft.Win32;

namespace LockCrypt {
    /// <summary>
    /// Password generator.
    /// </summary>
    public partial class WinPasswordGenerator : Window {
        public static readonly DependencyProperty GeneratedPasswordsProperty = DependencyProperty.Register("GeneratedPasswords", typeof(ObservableCollection<string>), typeof(WinPasswordGenerator), new PropertyMetadata(new ObservableCollection<string>()));
        /// <summary>
        /// Gets or sets the generated passwords (excluding those which have been cleared.
        /// </summary>
        /// <value>The generated passwords.</value>
        public ObservableCollection<string> GeneratedPasswords {
            get { return base.GetValue(GeneratedPasswordsProperty) as ObservableCollection<string>; }
            set { base.SetValue(GeneratedPasswordsProperty, value); }
        }

        public static readonly DependencyProperty IsEditorChildProperty = DependencyProperty.Register("IsEditorChild", typeof(bool), typeof(WinPasswordGenerator), new PropertyMetadata(false));
        /// <summary>
        /// Gets or sets a value indicating whether this window should display controls and menu items which indicate that the password generated is for a specific purpose (Eg: 'Use password' MenuItems and buttons)
        /// </summary>
        /// <value>
        /// 	<c>true</c> if the generated password is intended for an account or specific purpose, otherwise <c>false</c>.
        /// </value>
        public bool IsEditorChild {
            get { return Convert.ToBoolean(base.GetValue(IsEditorChildProperty)); }
            set { base.SetValue(IsEditorChildProperty, value); }
        }

        public string SelectedPassword { get; set; }

        /// <summary>
        /// Initializes a new <see cref="WinPasswordGenerator"/>.
        /// </summary>
        public WinPasswordGenerator() {
            InitializeComponent();
            lblBrackets.Text = I18nUtils.GetString("Strings", "BracketsFmt", "[], (), <>, {}");
            lblSpecialCharacters.Text = I18nUtils.GetString("Strings", "SpecialCharactersFmt", "!, \", £, $, %, etc");
            GeneratedPasswords = new ObservableCollection<string>();
        }

        /// <summary>
        /// Initializes the password analyzer when the Window loads.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void Window_Loaded(object sender, RoutedEventArgs e) {
            specification_Changed(sender, EventArgs.Empty);
        }

        /// <summary>
        /// Checks the data entered is valid.
        /// </summary>
        /// <returns><c>True</c> if the data is valid, otherwise <c>false</c></returns>
        private bool Validate() {
            bool valid = true;
            if(txtLength.Value == 0) {
                DialogBox.ShowAlert(this, I18nUtils.GetString("Errors", "ValueGreaterThanFmt", I18nUtils.GetString("Errors", "Length"), "1"), I18nUtils.GetString("Errors", "Error"));
                txtLength.Focus();
                valid = false;
            } else if(chkSpecificFormat.IsChecked == true && txtFormat.Text.Trim().Length == 0) {
                DialogBox.ShowAlert(this, I18nUtils.GetString("Errors", "SpecificFormatRequired"), I18nUtils.GetString("Errors", "Error"));
                txtFormat.Focus();
                valid = false;
            }
            return valid;
        }

        private void length_Changed(object sender, RoutedPropertyChangedEventArgs<decimal> e) {
            specification_Changed(sender, EventArgs.Empty);
        }

        /// <summary>
        /// Recalculates maxmimum entropy when the options change.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void specification_Changed(object sender, EventArgs e) {
            if(!IsLoaded)
                return;
            var spec = GetSpecification();
            analyserPredicted.BitStrength = PasswordAnalyzer.CalculateMaxEntropy(spec);
        }

        /// <summary>
        /// Generates the specified number of passwords.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void GeneratePassword(object sender, ExecutedRoutedEventArgs e) {
            if(Validate()) {
                var args = GetSpecification();
                try {
                    IEnumerable<string> generatedPasswords = PasswordGenerator.GeneratePasswords(args, Convert.ToInt32(txtNumberOfPasswords.Value));
                    foreach(var pass in generatedPasswords) {
                        this.GeneratedPasswords.Add(pass);
                    }
                    if(lstPasswords.HasItems) {
                        lstPasswords.SelectedIndex = lstPasswords.Items.Count - 1;
                    }
                } catch(LockCryptException ex) {
                    DialogBox.ShowAlert(this, ex.Message, I18nUtils.GetString("Errors", "Error"));
                }
                Settings.Default.Save();
            }
        }

        /// <summary>
        /// Gets a password specification from the selected options.
        /// </summary>
        /// <returns>A password specification representing the selected options.</returns>
        private PasswordCreationArgs GetSpecification() {
            var spec = new PasswordCreationArgs() {
                Brackets = chkBrackets.IsChecked == true,
                Digits = chkDigits.IsChecked == true,
                ExtendedAscii = chkExtendedAscii.IsChecked == true,
                LowerCase = chkLowercase.IsChecked == true,
                SpecialCharacters = chkSpecialCharacters.IsChecked == true,
                UpperCase = chkUppercase.IsChecked == true,
                Length = Convert.ToInt32(txtLength.Value)
            };
            spec.ExtraCharacters.AddRange(txtExtraCharacters.Text.ToCharArray().Distinct());
            if(chkSpecificFormat.IsChecked == true) {
                spec.PasswordMask = txtFormat.Text;
            }
            return spec;
        }

        /// <summary>
        /// Copies a password to the Clipboard.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void CopyToClip(object sender, ExecutedRoutedEventArgs e) {
            if(lstPasswords.SelectedValue == null)
                return;
            string selectedPass = (lstPasswords.SelectedValue ?? string.Empty).ToString();
            SecureClipboard.CopyToClipboard(selectedPass);
        }

        /// <summary>
        /// Fires the open command when an item is double clicked.
        /// </summary>
        private void lst_MouseDoubleClick(object sender, MouseButtonEventArgs e) {
            ApplicationCommands.Open.Execute(null, this);
        }

        /// <summary>
        /// Sets the SelectedPassword and closes the dialog if <c>IsEditorChildProperty</c> is <c>true</c>.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void SelectPassword(object sender, ExecutedRoutedEventArgs e) {
            if(IsEditorChild && lstPasswords.SelectedValue != null) {
                SelectedPassword = lstPasswords.SelectedValue.ToString();
                DialogResult = true;
            }
        }

        /// <summary>
        /// Saves the generated passwords to a text file.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void SavePasswords(object sender, ExecutedRoutedEventArgs e) {
            var dlgSave = new SaveFileDialog {
                ValidateNames = true,
                OverwritePrompt = true,
                 AddExtension=true,
                Filter = string.Format("{0}|*.*|{1} (*.txt)|*.txt",
                                       I18nUtils.GetString("Strings", "AllFiles"),
                                       I18nUtils.GetString("Strings", "TextFiles")),
                Title = I18nUtils.GetString("Strings", "Save")
            };
            var items = Settings.Default.MiscFileHistory.Split(new [] {'|'}, StringSplitOptions.RemoveEmptyEntries);
            try {
                if(items.Length > 0) {
                    string mostRecentFile = items.Last();
                    if(File.Exists(mostRecentFile)) {
                        dlgSave.FileName = mostRecentFile;
                    }
                }
            } catch {
            }
            if(dlgSave.ShowDialog() == true) {
                try {
                    File.WriteAllLines(dlgSave.FileName, GeneratedPasswords.ToArray(), Encoding.UTF8);
                } catch(Exception ex) {
                    DialogBox.ShowAlert(this, ex.Message, I18nUtils.GetString("Errors", "ErrorSaving"));
                }
            }
        }

        /// <summary>
        /// Clears the list of generated passwords.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void ClearPasswords(object sender, ExecutedRoutedEventArgs e) {
            GeneratedPasswords.Clear();
        }

        /// <summary>
        /// Clears the password list when the Window closes.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.ComponentModel.CancelEventArgs"/> instance containing the event data.</param>
        private void Window_Closing(object sender, CancelEventArgs e) {
            GeneratedPasswords.Clear();
        }

        /// <summary>
        /// Shows the help file.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void ShowHelp(object sender, ExecutedRoutedEventArgs e) {
            switch((e.Parameter ?? string.Empty).ToString().ToLower()) {
                case "format":
                    FlowDocument doc = new FlowDocument(new Paragraph(new Run(I18nUtils.GetString("Strings", "SpecificFormatInfo")))) {
                        IsEnabled = false
                    };
                    List<KeyValuePair<string, string>> shortcuts = new List<KeyValuePair<string, string>>() {
                        {new KeyValuePair<string,string>(I18nUtils.GetString("Strings", "Symbol"),I18nUtils.GetString("Strings", "Meaning"))},
                        {new KeyValuePair<string,string>("*",I18nUtils.GetString("Strings", "AnyCharacter"))},
                        {new KeyValuePair<string,string>("C",I18nUtils.GetString("Strings", "UppercaseCharactersFmt", "A-Z"))},
                        {new KeyValuePair<string,string>("c",I18nUtils.GetString("Strings", "LowercaseCharactersFmt", "a-z"))},
                        {new KeyValuePair<string,string>("d",I18nUtils.GetString("Strings", "ExtendedAsciiCharacters"))},
                        {new KeyValuePair<string,string>("b",I18nUtils.GetString("Strings", "DigitsFmt", "0-9"))},
                        {new KeyValuePair<string,string>("e",I18nUtils.GetString("Strings", "BracketsFmt", "[], (), <>, {}"))},
                        {new KeyValuePair<string,string>("s",I18nUtils.GetString("Strings", "SpecialCharactersFmt", "!, \", £, $, %, etc"))},
                        //{new KeyValuePair<string,string>("@",I18nUtils.GetString("Strings", "JumpToTabFmt"))},  custom characters?
                    };
                    doc.Blocks.Add(Lime49.WPF.Utils.GenerateFlowTable(shortcuts));
                    doc.IsEnabled = false;
                    DialogBox.Show(this, doc, I18nUtils.GetString("Strings", "ShortcutKeys"), false, DialogBox.DialogBoxType.OK, DialogBox.DialogBoxIcon.Information, DialogBoxButton.OK);
                    break;
                case "entropy":
                    System.Diagnostics.Process.Start("http://wiki.lime49.com/index.php?title=Password_Generator#Password_Analysis");
                    break;
                default:
                    WPFUtils.OpenHelp("Password.Generator.html");
                    break;
            }
        }

        /// <summary>
        /// Determines whether a password is selected.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.CanExecuteRoutedEventArgs"/> instance containing the event data.</param>
        private void IsItemSelected(object sender, CanExecuteRoutedEventArgs e) {
            e.CanExecute = IsLoaded && lstPasswords.SelectedValue != null;
        }
    }
}
